using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Threading;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.event
{


// TODO: rewrite this one completly, use new concurrent util stuff

///
/// <summary> * The EventManager is used to register/unregister event listeners and to
/// * generate events. There are two types of events and event listeners,
/// * pro-active and re-active. A generated event consists of
/// * <seealso cref="Event event type"/> and <seealso cref="EventData event data"/> and will be sent to
/// * the event listeners registered on the event type.
/// * <p>
/// * Pro-active events are synchronous, generating a pro-active event will tell
/// * all the pro-active event listeners registered on that event to negotiate and
/// * return an answer. This is a very useful way to ask about something but the
/// * caller don't have to know who needs to be asked. Generate a pro-active event
/// * and continue depending on what answer you get.
/// * <p>
/// * The other type of events, re-active events, are asynchronous. Generating a
/// * re-active event will just place the event in a queue. The re-active events
/// * are removed from the queue in the near future and sent to the re-active event
/// * listeners registered on that event. This is a good way to tell someone that
/// * something has been done without knowing who you have to tell. </summary>
/// 
	public class EventManager
	{
		private static Logger log = Logger.getLogger(typeof(EventManager).Name);

		private Map<Event, List<ProActiveEventListener>> proActiveEventListeners = new Dictionary<Event, List<ProActiveEventListener>>();
		private Map<Event, List<ReActiveEventListener>> reActiveEventListeners = new Dictionary<Event, List<ReActiveEventListener>>();
		private LinkedList<EventElement> eventElements = new LinkedList<EventElement>();

		private volatile bool startIsOk = true;
		private volatile bool destroyed = true;

		private EventQueue eventQueue;

		public EventManager()
		{
			eventQueue = new EventQueue(this);
		}

///    
///     <summary> * Generates a pro-active event to all pro-active event listeners registered
///     * on <CODE>event</CODE>. The event listeners will negotiate and an
///     * answer will be returned, synchronously.
///     * <p>
///     * The results returned from the registered event listeners are anded and
///     * returned, if there are no pro-active event listeners registered on 
///     * <CODE>event</CODE>, <CODE>true</CODE> will be returned.
///     *  </summary>
///     * <param name="event">
///     *            the event type </param>
///     * <param name="data">
///     *            the event data </param>
///     * <returns> the negotiated answers from the event listeners </returns>
///     
		public virtual bool generateProActiveEvent(Event @event, EventData data)
		{
			Debug.Assert(!destroyed);

		// TODO: fix this, use safe ArrayMap instead
			List<ProActiveEventListener> listenerList = proActiveEventListeners.get(@event);
			if (listenerList != null)
			{
				List<ProActiveEventListener>.Enumerator listItr = listenerList.GetEnumerator();
				bool result = true;
			// no concurrent mod, list is copied in modyfing blocks
				while (listItr.MoveNext() && result)
				{
					ProActiveEventListener listener = listItr.Current;
					try
					{
						if (!listener.proActiveEventReceived(@event, data))
						{
							result = false;
						}
					}
					catch (System.Exception t)
					{
						log.log(Level.SEVERE, "Exception sending pro-active event to " + listener, t);
						result = false;
					}
				}
				return result;
			}
			return true;
		}

///    
///     <summary> * Generates a re-active event to all re-active event listeners registered
///     * on <CODE>event</CODE>. The event will be sent to the listeners in the
///     * near future, asynchronous communication.
///     *  </summary>
///     * <param name="event">
///     *            the event type </param>
///     * <param name="data">
///     *            the event data </param>
///     
		public virtual void generateReActiveEvent(Event @event, EventData data)
		{
			Debug.Assert(!destroyed);
			markWithOriginatingThread(data);
			EventElement evtElement = new EventElement(@event, data, false);
			lock (eventElements)
			{
				eventElements.AddLast(evtElement);
			}
		}

		internal virtual EventElement getNextEventElement()
		{
			lock (eventElements)
			{
				if (!eventElements.Count == 0)
				{
					try
					{
						return eventElements.RemoveFirst();
					}
					catch (NoSuchElementException e)
					{
					// ok return null
					}
				}
			}
			return null;
		}

///    
///     <summary> * Registers a pro-active event listener to <CODE>event</CODE>.
///     *  </summary>
///     * <param name="listener">
///     *            the pro-active event listener to register </param>
///     * <param name="event">
///     *            the event to register <CODE>listener</CODE> to </param>
///     * <exception cref="EventListenerAlreadyRegisteredException">
///     *             if the <CODE>listener</CODE> is already registered </exception>
///     * <exception cref="EventListenerNotRegisteredException">
///     *             if <CODE>listener</CODE> or <CODE>event</CODE> is 
///     *             <CODE>null</CODE> </exception>
///     
//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized void registerProActiveEventListener(ProActiveEventListener listener, Event event) throws EventListenerAlreadyRegisteredException, EventListenerNotRegisteredException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void registerProActiveEventListener(ProActiveEventListener listener, Event @event)
		{
			Debug.Assert(!destroyed);
			if (listener == null || @event == null)
			{
				throw new EventListenerNotRegisteredException("Null parameter, listener=" + listener + ", event=" + @event);
			}
			if (proActiveEventListeners.containsKey(@event))
			{
				List<ProActiveEventListener> listenerList = proActiveEventListeners.get(@event);
				if (!listenerList.Contains(listener))
				{
					List<ProActiveEventListener> newList = new List<ProActiveEventListener>();
					newList.AddRange(listenerList);
					newList.Add(listener);
					proActiveEventListeners.put(@event, newList);
				}
				else
				{
					throw new EventListenerAlreadyRegisteredException(" listener=" + listener + ", event=" + @event);
				}
			}
			else
			{
				List<ProActiveEventListener> listenerList = new List<ProActiveEventListener>();
				listenerList.Add(listener);
				proActiveEventListeners.put(@event, listenerList);
			}
		}

///    
///     <summary> * Removes a pro-active event listener from <CODE>event</CODE>.
///     *  </summary>
///     * <param name="listener">
///     *            the pro-active event listener </param>
///     * <param name="event">
///     *            the event to remove <CODE>listener</CODE> from </param>
///     * <exception cref="EventListenerNotRegisteredException">
///     *             if <CODE>listener</CODE> is no registered </exception>
///     
//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized void unregisterProActiveEventListener(ProActiveEventListener listener, Event event) throws EventListenerNotRegisteredException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void unregisterProActiveEventListener(ProActiveEventListener listener, Event @event)
		{
			Debug.Assert(!destroyed);
			if (proActiveEventListeners.containsKey(@event))
			{
				List<ProActiveEventListener> listenerList = proActiveEventListeners.get(@event);
				if (listenerList.Contains(listener))
				{
					List<ProActiveEventListener> newList = new List<ProActiveEventListener>();
					newList.AddRange(listenerList);
					newList.Remove(listener);
					proActiveEventListeners.put(@event, newList);
					if (newList.Count == 0)
					{
						proActiveEventListeners.Remove(@event);
					}
				}
				else
				{
					throw new EventListenerNotRegisteredException(" listener=" + listener + ", event=" + @event);
				}
			}
			else
			{
				throw new EventListenerNotRegisteredException(" listener=" + listener + ", event=" + @event);
			}
		}

///    
///     <summary> * Registers a re-active event listener to <CODE>event</CODE>.
///     *  </summary>
///     * <param name="listener">
///     *            the re-active event listener to register </param>
///     * <param name="event">
///     *            the event to register <CODE>listener</CODE> to </param>
///     * <exception cref="EventListenerAlreadyRegisteredException">
///     *             if the <CODE>listeners</CODE> is already registered </exception>
///     * <exception cref="EventListenerNotRegisteredException">
///     *             if <CODE>listener</CODE> or <CODE>event</CODE> is 
///     *             <CODE>null</CODE> </exception>
///     
//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized void registerReActiveEventListener(ReActiveEventListener listener, Event event) throws EventListenerAlreadyRegisteredException, EventListenerNotRegisteredException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void registerReActiveEventListener(ReActiveEventListener listener, Event @event)
		{
			Debug.Assert(!destroyed);
			if (listener == null || @event == null)
			{
				throw new EventListenerNotRegisteredException("Null parameter, listener=" + listener + ", event=" + @event);
			}
			if (reActiveEventListeners.containsKey(@event))
			{
				List<ReActiveEventListener> listenerList = reActiveEventListeners.get(@event);
				if (!listenerList.Contains(listener))
				{

					List<ReActiveEventListener> newList = new List<ReActiveEventListener>();
					newList.AddRange(listenerList);
					newList.Add(listener);
					reActiveEventListeners.put(@event, newList);
				}
				else
				{
					throw new EventListenerAlreadyRegisteredException(" listener=" + listener + ", event=" + @event);
				}
			}
			else
			{
				List<ReActiveEventListener> listenerList = new List<ReActiveEventListener>();
				listenerList.Add(listener);
				reActiveEventListeners.put(@event, listenerList);
			}
		}

///    
///     <summary> * Removes a re-active event listener from <CODE>event</CODE>.
///     *  </summary>
///     * <param name="listener">
///     *            the re-active event listener </param>
///     * <param name="event">
///     *            the event to remove <CODE>listener</CODE> from </param>
///     * <exception cref="EventListenerNotRegisteredException">
///     *             if <CODE>listener</CODE> is no registered </exception>
///     
//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized void unregisterReActiveEventListener(ReActiveEventListener listener, Event event) throws EventListenerNotRegisteredException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void unregisterReActiveEventListener(ReActiveEventListener listener, Event @event)
		{
			Debug.Assert(!destroyed);
			if (reActiveEventListeners.containsKey(@event))
			{
				List<ReActiveEventListener> listenerList = reActiveEventListeners.get(@event);
				if (listenerList.Contains(listener))
				{
					List<ReActiveEventListener> newList = new List<ReActiveEventListener>();
					newList.AddRange(listenerList);
					newList.Remove(listener);
					reActiveEventListeners.put(@event, newList);
					if (newList.Count == 0)
					{
						reActiveEventListeners.Remove(@event);
					}
				}
				else
				{
					throw new EventListenerNotRegisteredException(" listener=" + listener + ", event=" + @event);
				}
			}
			else
			{
				throw new EventListenerNotRegisteredException(" listener=" + listener + ", event=" + @event);
			}
		}

		internal virtual void sendReActiveEvent(Event @event, EventData data)
		{
		// TODO: fix this, use safe ArrayMap instead
			List<ReActiveEventListener> listeners = reActiveEventListeners.get(@event);
			if (listeners != null)
			{
				List<ReActiveEventListener>.Enumerator listItr = listeners.GetEnumerator();
				while (listItr.MoveNext())
				{
					ReActiveEventListener listener = listItr.Current;
					try
					{
						listener.reActiveEventReceived(@event, data);
					}
					catch (System.Exception t)
					{
						t.printStackTrace();
						log.severe("Exception sending re-active event to " + listener);
					}
				}
			}
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual void start()
		{
			if (startIsOk)
			{
				eventQueue.Start();
				startIsOk = false;
				destroyed = false;
			}
			else
			{
				log.warning("EventModule already started");
			}
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual void stop()
		{
			Debug.Assert(!destroyed);
			if (!startIsOk)
			{
				startIsOk = true;
				eventQueue.shutdown();
				eventQueue = new EventQueue(this);
			}
			else
			{
				log.warning("EventModule already stopped");
			}
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual void destroy()
		{
			Debug.Assert(!destroyed);
			if (startIsOk)
			{
				removeListeners();
				destroyed = true;
				proActiveEventListeners = new Dictionary<Event, List<ProActiveEventListener>>();
				reActiveEventListeners = new Dictionary<Event, List<ReActiveEventListener>>();
				eventElements = new LinkedList<EventElement>();
				startIsOk = true;
				destroyed = true;
			}
			else
			{
				log.severe("EventModule not in stopped state");
			}
		}

		internal virtual void setReActiveEventQueueWaitTime(int time)
		{
			Debug.Assert(!destroyed);
			eventQueue.setWaitTime(time);
		}

		internal virtual int getReActiveEventQueueWaitTime()
		{
			Debug.Assert(!destroyed);
			return eventQueue.getWaitTime();
		}

		internal virtual void setReActiveEventQueueNotifyOnCount(int count)
		{
			Debug.Assert(!destroyed);
			eventQueue.setNotifyOnCount(count);
		}

		internal virtual int getReActiveEventQueueNotifyOnCount()
		{
			Debug.Assert(!destroyed);
			return eventQueue.getNotifyOnCount();
		}

		private void markWithOriginatingThread(EventData data)
		{
			if (data != null)
			{
				data.setOriginatingThread(Thread.CurrentThread);
			}
		}

		private void removeListeners()
		{
			IEnumerator <Event> itr = proActiveEventListeners.Keys.GetEnumerator();
			while (itr.MoveNext())
			{
				Event @event = itr.Current;
				List<ProActiveEventListener> listenerList = proActiveEventListeners.get(@event);
				StringBuilder stringList = new StringBuilder();
				for (int i = 0; i < listenerList.Count; i++)
				{
					stringList.Append(listenerList[i]);
					stringList.Append("\n");
				}
			}
			itr = reActiveEventListeners.Keys.GetEnumerator();
			while (itr.MoveNext())
			{
				Event @event = itr.Current;
				List<ReActiveEventListener> listenerList = reActiveEventListeners.get(@event);
				StringBuilder stringList = new StringBuilder();
				for (int i = 0; i < listenerList.Count; i++)
				{
					stringList.Append(listenerList[i]);
					stringList.Append("\n");
				}
			}
		}
	}
}